//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>
#include "ata.h"
#include "parttab.h"
#include "idectrlr.h"
#include "idedrive.h"
#include "idepart.h"

//
// IDEDrive
//
ECode IDEDrive::Read(
    /* [in] */ Int64 u64Offset,
    /* [in] */ Int32 bytesToRead,
    /* [out] */ MemoryBuf * pBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (pBuffer == NULL
        || (int)bytesToRead > pBuffer->GetCapacity()
        || 0 == bytesToRead
        || 0 != (bytesToRead & SECTOR_MASK)
        || 0 != (u64Offset &SECTOR_MASK)) {
        return E_INVALID_ARGUMENT;
    }

    uint_t uNumberOfSectorsRead;
    ECode ec = ReadSectors(
        (uint32_t)(u64Offset >> SECTOR_SHIFT), (uint8_t *)pBuffer->GetPayload(),
        bytesToRead >> SECTOR_SHIFT,
        &uNumberOfSectorsRead);

    pBuffer->SetUsed(uNumberOfSectorsRead << SECTOR_SHIFT);

    // TODO:
    if (NULL != ppCompletionEvent) *ppCompletionEvent = NULL;
    return ec;
}

ECode IDEDrive::Write(
    /* [in] */ Int64 u64Offset,
    /* [in] */ const MemoryBuf & buffer,
    /* [out] */ Int32 * pBytesWritten,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    if (buffer.IsNullOrEmpty()
        || 0 != (buffer.GetUsed() & SECTOR_MASK)
        || 0 != (u64Offset & SECTOR_MASK)
        || NULL == pBytesWritten) {
        return E_INVALID_ARGUMENT;
    }

    uint_t uNumberOfSectorsWritten;
    ECode ec = WriteSectors(
        (uint32_t)(u64Offset >> SECTOR_SHIFT), (uint8_t *)buffer.GetPayload(),
        (uint_t)buffer.GetUsed() >> SECTOR_SHIFT,
        &uNumberOfSectorsWritten);

    *pBytesWritten = uNumberOfSectorsWritten << SECTOR_SHIFT;

    if (NULL != ppCompletionEvent) *ppCompletionEvent = NULL;
    return ec;
}

ECode IDEDrive::ReadSectors(
    uint32_t u32LogicalSectorNo, uint8_t *pu8Buffer,
    uint_t uNumberOfSectorsToRead, uint_t *puNumberOfSectorsRead)
{
    return m_pController->ReadSectors(
        m_uDriveNo, u32LogicalSectorNo, pu8Buffer,
        uNumberOfSectorsToRead, puNumberOfSectorsRead);
}

ECode IDEDrive::WriteSectors(
    uint32_t u32LogicalSectorNo, uint8_t *pu8Buffer,
    uint_t uNumberOfSectorsToWrite, uint_t *puNumberOfSectorsWritten)
{
    return m_pController->WriteSectors(
        m_uDriveNo, u32LogicalSectorNo, pu8Buffer,
        uNumberOfSectorsToWrite, puNumberOfSectorsWritten);
}

ECode IDEDrive::ReadSectors(
    uint16_t u16CylinderNo, uint8_t u8HeadNo,
    uint8_t u8SectorNo, uint8_t *pu8Buffer,
    uint_t uNumberOfSectorsToRead, uint_t *puNumberOfSectorsRead)
{
    return m_pController->ReadSectors(
        m_uDriveNo, u16CylinderNo, u8HeadNo, u8SectorNo, pu8Buffer,
        uNumberOfSectorsToRead, puNumberOfSectorsRead);
}

ECode IDEDrive::WriteSectors(
    uint16_t u16CylinderNo, uint8_t u8HeadNo,
    uint8_t u8SectorNo, uint8_t *pu8Buffer,
    uint_t uNumberOfSectorsToWrite, uint_t *puNumberOfSectorsWritten)
{
    return m_pController->WriteSectors(
        m_uDriveNo, u16CylinderNo, u8HeadNo, u8SectorNo, pu8Buffer,
        uNumberOfSectorsToWrite, puNumberOfSectorsWritten);
}

ECode IDEDrive::Control(
    /* [in] */ Handle32 nControlCode,
    /* [in] */ const MemoryBuf & inBuffer,
    /* [out] */ MemoryBuf * pOutBuffer,
    /* [out] */ IEvent * * ppCompletionEvent)
{
    // TODO:

    return E_NOT_IMPLEMENTED;
}

void IDEDrive::Dispose()
{
    delete this;
}

IDEDrive::IDEDrive(IDEController *pController, uint_t uDeviceNo)
{
    assert(pController);
    m_pController   = pController;
    m_uDeviceNo     = uDeviceNo;
    m_uDriveNo      = m_uDeviceNo & 1;

    m_uNumberOfPartInfos = 0;
    m_partInfoListHead.Initialize();
    m_u32StartSectorOfFirstExtPartTab = 0;

    InitPartitions();
}

PartitionInfo *IDEDrive::GetPartitionInfo(uint_t uDeviceNo)
{
    PartitionInfo *pPartInfo;
    ForEachDLinkNode(PartitionInfo *, pPartInfo, &m_partInfoListHead) {
        if (pPartInfo->m_uDeviceNo == uDeviceNo) {
            return pPartInfo;
        }
    }
    return NULL;
}

ECode IDEDrive::InitPartitions()
{
    Partition parttab[c_uNumberOfPartTabEntries];
    ECode ec = ReadPartitionTable(0, parttab);
    if (FAILED(ec)) return ec;

    for (uint_t i = 0; i < c_uNumberOfPartTabEntries; i++) {
        InitPartition(0, parttab[i]);
    }

#ifndef _RELEASE
    PartitionInfo *pPartInfo;
    ForEachDLinkNode(PartitionInfo *, pPartInfo, &m_partInfoListHead) {
        kprintf("    <Partition %d> Start: %x , Length: %x Sectors\n",
                pPartInfo->m_uDeviceNo,
                pPartInfo->m_u32StartSector ,
                pPartInfo->m_u32TotalSectors);
    }
#endif // _RELEASE
    return NOERROR;
}

ECode IDEDrive::InitPartition(
    uint32_t u32StartSectorNoOfPartTab, const Partition &partition)
{
    ECode ec;
    PartitionInfo *pPartInfo;
    uint32_t u32StartSector;

    switch (partition.u8SystemId) {
        case PartitionType_Empty:
            break;

        case PartitionType_Extended:
        case PartitionType_Extended2:
            u32StartSector = MKDWORD(
            partition.u16RelativeSectorLow, partition.u16RelativeSectorHigh);

            if (0 != u32StartSectorNoOfPartTab
                && 0 == m_u32StartSectorOfFirstExtPartTab) {
                m_u32StartSectorOfFirstExtPartTab = u32StartSectorNoOfPartTab;
            }

            u32StartSector += m_u32StartSectorOfFirstExtPartTab;

//        kprintf("Found a extended Partition in %x\n",
//                u32RelativeSector << SECTOR_SHIFT);

            Partition parttab[c_uNumberOfPartTabEntries];
            ec = ReadPartitionTable(u32StartSector, parttab);
            if (FAILED(ec)) {
                kprintf("*ERROR* Failed to read extended partition table\n");
                return ec;
            }

            for (uint_t i = 0; i < c_uNumberOfPartTabEntries; i++) {
                InitPartition(u32StartSector, parttab[i]);
            }
            break;

        case PartitionType_FAT12Primary:
        case PartitionType_FAT16Primary:
        case PartitionType_BIGDOSFATPrimary:
        case PartitionType_NTFSPrimary:
        case PartitionType_FAT32Primary:
        case PartitionType_FAT32Primary2:
        case PartitionType_FAT16Primary2:
        default:
            u32StartSector = u32StartSectorNoOfPartTab + MKDWORD(
            partition.u16RelativeSectorLow, partition.u16RelativeSectorHigh);

//        kprintf("Found a non-extended Partition in %x\n",
//                u32RelativeSector << SECTOR_SHIFT);

            pPartInfo = new PartitionInfo(
            MKDWORD(m_uNumberOfPartInfos, m_uDeviceNo),
            u32StartSector,
            MKDWORD(partition.u16TotalSectorsLow,
                    partition.u16TotalSectorsHigh));

            m_uNumberOfPartInfos++;
            m_partInfoListHead.InsertLast(pPartInfo);

            DzRegisterDevice(DEVICENAME_IDEPARTITION, pPartInfo->m_uDeviceNo,
                            this);

            break;
    }

    return NOERROR;
}

ECode IDEDrive::ReadPartitionTable(
    uint32_t u32LogicalSectorNo,
    Partition parttab[c_uNumberOfPartTabEntries])
{
    assert(SECTOR_SIZE == sizeof(PartitionTableSector));

    PartitionTableSector partitionTableSector;
    uint_t uNumberOfSectorsRead = 0;
    ECode ec = ReadSectors(
            u32LogicalSectorNo, (uint8_t *)&partitionTableSector,
            1, &uNumberOfSectorsRead);
    if (FAILED(ec)) {
        kprintf("*ERROR* Failed to read partition table sector\n");
        return E_FAIL;
    }

    assert(1 == uNumberOfSectorsRead);

    if (EXECUTABLE_MARKER != partitionTableSector.u16ExecutableMarker) {
        kprintf("*ERROR* No partition table sector\n");
        return E_FAIL;
    }

    for (uint_t i = 0; i < c_uNumberOfPartTabEntries; i++) {
        parttab[i] = partitionTableSector.parttab[i];
    }

    return NOERROR;
}

EXTERN IDeviceDriver * CDECL CreateIDEDrive(uint_t uDeviceNo, void *pvParameter)
{
    if (NULL == pvParameter) return NULL;

    IDEDrive *pIDEDrive = new IDEDrive(
        (IDEController *)pvParameter, uDeviceNo);
    if (NULL == pIDEDrive) return NULL;

    pIDEDrive->AddRef();
    return pIDEDrive;
}
